package org.fujene.communicate;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.logging.Level;
import org.fujene.communicate.console.Console;
import org.fujene.communicate.deleter.DeleterClient;
import org.fujene.communicate.deleter.DeleterHost;
import org.fujene.communicate.deleter.DeleterPackage;
import org.fujene.communicate.filter.SimpleFilter;
import org.fujene.communicate.hash.HashCompareTool;
import org.fujene.communicate.hash.Hashing;
import org.fujene.communicate.indexer.content.ContentIndexerClient;
import org.fujene.communicate.indexer.content.ContentIndexerHost;
import org.fujene.communicate.indexer.content.ContentIndexerPackage;
import org.fujene.communicate.indexer.term.TermIndexerClient;
import org.fujene.communicate.indexer.term.TermIndexerHost;
import org.fujene.communicate.indexer.term.TermPackage;
import org.fujene.communicate.lock.IndexableSwitch;
import org.fujene.communicate.node.Node;
import org.fujene.communicate.rpc.Communicator;
import org.fujene.communicate.rpc.PeerHandler;
import org.fujene.communicate.searcher.TermSearcherHost;
import org.fujene.communicate.searcher.SearcherPackage;
import org.fujene.constants.Constants;
import org.fujene.index.IndexService;
import org.fujene.index.IndexingPackage;
import org.fujene.logging.LogParser;
import org.fujene.memman.MemoryManager;
import org.fujene.memman.TermInformation;
import org.fujene.memman.backup.InvertIndexSnapshoter;
import org.fujene.memman.backup.TermSnapshoter;
import org.fujene.memoryblock.dictionary.DictionaryPackage;
import org.fujene.search.SearchPackage;
import org.fujene.search.SearchResult;
import org.fujene.search.SearchResultPackage;
import org.fujene.search.SearchService;
import org.fujene.structure.ContentInfo;
import org.fujene.structure.LookupPointer;
import org.fujene.term.Term;
import org.fujene.term.TermList;
import org.fujene.toolbox.ByteConverter;
import org.msgpack.object.ArrayType;
import org.msgpack.rpc.Server;
import org.msgpack.rpc.loop.EventLoop;

public final class MainService{
	// MemoryManager -> Memory, Disk
	private MemoryManager memman;
	private MainServiceSettings settings;

	private Server server;
	private IndexService indexServer;
	private SearchService searchServer;
	private Thread[] indexThreads;
	private Thread beaconThread, apiThread;
	private ExecutorService execIndex, execSearch, execBeacon, execApi;
	private String[] firstHost;
	private int[] firstPorts;
	private Communicator communicator;
	private SimpleFilter filter;
	private int indexCounter, searchCounter;

	public void initialize() throws IOException, InterruptedException{
		// get my IPv4 address
		Enumeration<NetworkInterface> enulfs = NetworkInterface.getNetworkInterfaces();
		if(!enulfs.hasMoreElements()) return;
		NetworkInterface ni = enulfs.nextElement();
		Enumeration<InetAddress> enuAddr = ni.getInetAddresses();
		if(!enuAddr.hasMoreElements()) return;
		// FIXME: choose IPv4/IPv6 by setting
		InetAddress addrInfo = enuAddr.nextElement();
		while(!(addrInfo instanceof Inet4Address))
			addrInfo = enuAddr.nextElement();

		String address = addrInfo.getHostAddress();
		Node.property(settings.virtualNodes(), settings.beaconThreshold());
		Node myNode = Node.createLocal(address, settings.peerport());
		// Add myself
		settings.setMyNode(myNode);
		communicator = new Communicator();
		if(firstHost != null) getInformation();

		// Initial value, if user don't set content set
		if(settings.contentInfo() == null)
			settings.setContentInfo(new ContentInfo(new String[]{"content"}, new String[0], new String[0],
				new String[0]));

		server = new Server(EventLoop.defaultEventLoop());
		server.serve(new PeerHandler(this));
		server.listen(settings.peerport());

		// temporary value, this values must exceed 6, should be multiplier of 6
		settings.setIndexThreadNum(12);
		settings.setSearchThreadNum(5);
		// Index threads
		execIndex = Executors.newFixedThreadPool(settings.indexThreadNum());
		indexThreads = new Thread[settings.indexThreadNum()];
		for(int i = 0; i < indexThreads.length; i++){
			if(i % 6 == 0) indexThreads[i] = new Thread(new ContentIndexerHost());
			else if(i % 6 == 1) indexThreads[i] = new Thread(new ContentIndexerClient(settings.clientLog()));
			else if(i % 6 == 2) indexThreads[i] = new Thread(new TermIndexerHost());
			else if(i % 6 == 3) indexThreads[i] = new Thread(new TermIndexerClient(settings.clientLog()));
			else if(i % 6 == 4) indexThreads[i] = new Thread(new DeleterHost());
			else if(i % 6 == 5) indexThreads[i] = new Thread(new DeleterClient());
			indexThreads[i].setDaemon(true);
			execIndex.execute(indexThreads[i]);
		}

		// Search threads
		execSearch = Executors.newFixedThreadPool(settings.searchThreadNum());

		execBeacon = Executors.newFixedThreadPool(1);
		beaconThread =
			new Thread(new BeaconService(myNode, settings.nodeList(), this, settings.beaconInterval(),
				15 * 60, communicator));
		beaconThread.setDaemon(true);
		execBeacon.execute(beaconThread);

		searchServer = new SearchService(this, settings.searchport());
		searchServer.initService();
		indexServer = new IndexService(this, settings.contentInfo(), settings.indexport());
		indexServer.initService();

		execApi = Executors.newFixedThreadPool(1);
		apiThread = new Thread(new MainHTMLServer(this, settings.contentInfo()));
		apiThread.setDaemon(true);
		execApi.execute(apiThread);

		filter = new SimpleFilter();

		// globally-use switch, initializes at here just once
		IndexableSwitch.initialize();

		int cluster = 0;
		if(firstHost != null){
			cluster = registerNode();
			// callForData();
		}else refreshHashList();

		// Start up memory manager
		memman =
			new MemoryManager(settings.blockSize(), settings.blockNum(), settings.directory(),
				settings.contentInfo(), settings.backupInterval(), settings.replication(),
				settings.getNodeAmount(), cluster, settings.uniqueIndex(), settings.deleteType(),
				settings.deleteIndex(), this);

		indexCounter = searchCounter = 0;

		synchronizeClock();
	}

	private boolean getArgs(String[] args){
		boolean isEnded = false;
		settings = MainServiceSettings.initWithDefault();

		if(args.length < 2){
			System.err
				.println("Usage: MainService [-p(--primary) <SettingFile> | -s(--secondary) [-f <SettingFile>] <Address:port ...>]");
			return isEnded;
		}

		if(args.length > 1 && (args[0].equals("--primary") || args[0].equals("-p"))){
			File settingDesc = new File(args[1]);
			if(settingDesc.exists()) settings.readSetting(args[1]);
			else System.out.println("Setting file not found. Skip.");
			isEnded = true;
		}

		// Secondary mode
		if(args.length > 1 && (args[0].equals("--secondary") || args[0].equals("-s"))){
			int hostIndex = 1;
			// Find port file
			if(args[1].equals("-f")){
				hostIndex = 3;
				// Check whether setting file exists
				File settingDesc = new File(args[2]);
				if(settingDesc.exists()) settings.readSetting(args[2]);
				else System.out.println("Setting file not found. Skip.");
			}
			firstHost = new String[args.length - hostIndex];
			firstPorts = new int[args.length - hostIndex];
			for(int i = hostIndex; i < args.length; i++){
				String[] hostSet = args[i].split(":");
				if(hostSet.length != 2) return isEnded;
				firstHost[i - hostIndex] = hostSet[0];
				firstPorts[i - hostIndex] = Integer.parseInt(hostSet[1]);
			}
			// logger
			settings.setDirectory("./faredata/");
			try{
				LogParser.setParser(Level.WARNING.getName(), "faredata/faredb.log");
			}catch(SecurityException e){}catch(IOException e){}

			isEnded = true;
		}

		return isEnded;
	}

	public static void main(String[] args) throws InterruptedException, IOException{
		MainService mainSystem = new MainService();
		if(!mainSystem.getArgs(args)) return;
		mainSystem.initialize();
		mainSystem.setProperties();
		IndexableSwitch.unlock();
		new Console(mainSystem).console();
	}

	private void setProperties(){
		TermIndexerHost.property(settings.replication(), settings.myNode(), settings.nodeList());
		TermIndexerClient.property(memman);
		ContentIndexerHost.property(settings.replication(), settings.myNode(), settings.nodeList(),
			communicator);
		ContentIndexerClient.property(memman);
		DeleterHost.property(settings.replication(), settings.myNode(), settings.nodeList());
		DeleterClient.property(memman);
		TermSearcherHost.property(settings.replication(), memman, settings.myNode(), settings.nodeList(),
			communicator);
		HashCompareTool.registerLists(settings.replication(), settings.myNode(), settings.nodeList());
	}

	// Tester's startup method
	public void testMain(String[] args) throws InterruptedException, IOException{
		settings = MainServiceSettings.initWithTest();
		LogParser.setParser(Level.WARNING.getName(), "orbis_data");
		this.initialize();
		this.setProperties();
		IndexableSwitch.unlock();
	}

	// Process to stop all works
	public void setStopper(boolean isStop){
		System.out.println("Shutting down...");
		searchServer.stopService();
		indexServer.stopService();
		execIndex.shutdownNow();
		execSearch.shutdownNow();
		execBeacon.shutdownNow();
		// Add header
		memman.saveMemory();

		for(Node eachNode: settings.nodeList()){
			if(!eachNode.equals(settings.myNode()))
				communicator.finalizeProcess(eachNode, settings.myNode());
		}
		settings.setNodeList(null);
		// FIXME: Better way to stop gracefully
		Runtime.getRuntime().exit(0);
	}

	private long lastTime = -1L;
	private int sequence = 0;

	// Host indexing, make index ID to have ordered indexing
	public void setNewIndex(IndexingPackage newIndex){
		indexCounter++;
		long nowTime = System.currentTimeMillis();
		if(lastTime == nowTime) sequence++;
		else sequence = 0;
		lastTime = nowTime;
		newIndex
			.setID((nowTime << Constants.ID_SHIFT) + (sequence << Constants.SEQ_SHIFT) + getMyNodeIndex());
		ContentIndexerHost.addQueue(new ContentIndexerPackage(newIndex.ID(), newIndex.contents(), newIndex
			.appendices()));
		for(int i = 0; i < newIndex.contentLength(); i++){
			long startTime = LogParser.initTime();
			TermList terms = settings.chopper().chop(newIndex.content(i));
			LogParser.timerFinest("Index / Chop", startTime);
			for(Term term: terms)
				TermIndexerHost.addToQueue(TermPackage.valueOf(term.str(), newIndex.ID(), term.freq(), i,
					newIndex.binaryFlags(), newIndex.multipleFlags()));
		}
	}

	public void setDelete(int flagval){
		if(settings.deleteType() == MainServiceSettings.DELTYPE_MULTIFLAG
			|| settings.deleteType() == MainServiceSettings.DELTYPE_TIME)
			DeleterHost.addToQueue(new DeleterPackage(flagval));
	}

	public void setDelete(long ID){
		if(settings.deleteType() == MainServiceSettings.DELTYPE_ID)
			DeleterHost.addToQueue(new DeleterPackage(ID));
	}

	public void setDelete(String strval){
		if(settings.deleteType() == MainServiceSettings.DELTYPE_CONTENT
			|| settings.deleteType() == MainServiceSettings.DELTYPE_APPENDIX)
			DeleterHost.addToQueue(new DeleterPackage(strval));
	}

	// TODO: consider for multi thread searching
	// search process
	public SearchResultPackage setNewSearch(SearchPackage newQuery) throws Exception{
		searchCounter++;
		long dbgtime = LogParser.initTime();
		TermList terms = settings.chopper().chop(newQuery.query());
		SearcherPackage structure = SearcherPackage.newSearcher(terms, newQuery.flags(), 10);
		SearchResultPackage pkg = SearchResultPackage.make(settings.contentInfo(), newQuery);
		dbgtime = LogParser.timerFinest("Search / Chop", dbgtime);

		// starts search on specified node
		for(int i = 0; i < structure.getCount(); i++)
			execSearch.execute(new TermSearcherHost(structure, i, newQuery.searchTypes()));
		dbgtime = LogParser.timerFinest("Search / Sending process", dbgtime);
		// intersection if all nodes finished search
		structure.waitLatch();
		LogParser.timerFinest("Search / Search terms", dbgtime);

		if(!structure.isNotFound()){
			try{
				dbgtime = LogParser.initTime();
				// intersection
				long[] merged = new long[0];
				if(terms.size() > 1){
					// result by multiple terms needs intersection
					if(newQuery.isAndSearch()) merged = filter.intersection(null, structure.getAllResult());
					else merged = filter.join(null, structure.getAllResult());
					dbgtime = LogParser.timerFinest("Search / Intersection", dbgtime);
					newQuery.setLength(merged);
				}else{
					// only one term need not intersect, to get result faster
					merged = structure.getResult(0);
					newQuery.setLength(merged);
				}

				for(int i = newQuery.from(); i < Math.min(merged.length, newQuery.from() + newQuery.length()); i++){
					pkg.setResult(getContent(merged[i]), i);
				}
				dbgtime = LogParser.timerFinest("Search / Content", dbgtime);

				LogParser.timerFinest("Search / Get content", dbgtime);
			}catch(Exception e){
				e.printStackTrace();
			}
		}else{
			LogParser.fine("Not found.");
			newQuery.setLength(null);
		}

		return pkg;
	}

	private SearchResult getContent(long ID){
		long hash = Hashing.getContentHash(Long.valueOf(ID));
		TreeMap<Long, Node> nearestHashes = new TreeMap<Long, Node>();
		for(Node eachNode: settings.nodeList())
			nearestHashes.put(eachNode.getAttributeHash(hash), eachNode);
		Entry<Long, Node> nearestEntry = nearestHashes.ceilingEntry(Long.valueOf(hash));
		if(nearestEntry == null) nearestEntry = nearestHashes.firstEntry();
		Node nearestNode = nearestEntry.getValue();
		int cluster = settings.getNodeIndex(nearestNode);
		Entry<Long, Node> lookupEntry = nearestHashes.firstEntry();

		if(lookupEntry.getValue() == settings.myNode()){
			return memman.searchContent(ID, cluster);
		}else{
			for(int i = 0; i < settings.replication(); i++){
				SearchResult result =
					communicator.getContentSet(ID, contentInfo().contentsNum(), contentInfo().appendixNum(),
						lookupEntry.getValue(), cluster);
				if(result != null) return result;
				lookupEntry = settings.prevHashEntry(lookupEntry.getKey());
			}
		}
		return null;
	}

	public void readMemory(){
		memman.initializeMemory();
	}

	public void saveMemory(){
		memman.saveMemory();
	}

	// ///////////////////// Implement for P2P service //////////////////////
	/** Invokes process to index content from communicator.
	 * 
	 * @param queue - Informations of content */
	public void invokeIndexContent(ContentIndexerPackage queue){
		// System.out.println("Called " + queue.content(0));
		// run with less thread
		ContentIndexerClient.addToQueue(queue);
	}

	/** Invokes process to index terms from communicator.
	 * 
	 * @param str - term
	 * @param termfreq - term frequency in one content
	 * @param ID - content ID
	 * @param valueType - content's type */
	public void invokeIndexTerm(TermPackage pkg){
		// run with less thread
		TermIndexerClient.addToQueue(pkg);
	}

	/** Invokes process to search term.
	 * 
	 * @param query - search query
	 * @param valueType - content's type
	 * @return result for search in <code>ExtendTerm</code> */
	public TermInformation invokeSearch(String query, byte[] valueType, int cluster){
		if(!IndexableSwitch.isIndexable()) try{
			IndexableSwitch.waitIfLocked();
		}catch(InterruptedException ignored){}
		return memman.searchTerm(query, valueType, cluster);
	}

	public long[] invokeInvertIndex(LookupPointer ptr, SearchFlags flags){
		return memman.getInvertIndex(ptr, flags);
	}

	public void invokeDelete(long ID, int cluster){
		DeleterPackage pkg = new DeleterPackage(ID);
		pkg.setCluster(cluster);
		DeleterClient.addToQueue(pkg);
	}

	public void invokeDelete(String value, int cluster){
		DeleterPackage pkg = new DeleterPackage(value);
		pkg.setCluster(cluster);
		DeleterClient.addToQueue(pkg);
	}

	public void invokeDelete(int value, int cluster){
		DeleterPackage pkg = new DeleterPackage(value);
		pkg.setCluster(cluster);
		DeleterClient.addToQueue(pkg);
	}

	public Future<ArrayList<DictionaryPackage>> getTermSnapshot(long hashFrom, long hashTo, int valueType){
		ExecutorService snapperService = Executors.newCachedThreadPool();

		return snapperService.submit(new TermSnapshoter(memman, hashFrom, hashTo, valueType));
	}

	public LinkedHashMap<Long, Integer> getContentsByHash(LookupPointer ptr, int length){
		return null;
	}

	public Future<LinkedHashMap<Long, Integer>> getInvIndexSnapshot(LookupPointer ptr, int length){
		ExecutorService snapperService = Executors.newCachedThreadPool();

		return snapperService.submit(new InvertIndexSnapshoter(memman, ptr, length));
	}

	public ArrayType putInfo(){
		ArrayType info = settings.getNodeInfo();
		return info;
	}

	public int getQueueSize(int type){
		if(type == 0) return ((ThreadPoolExecutor)execIndex).getPoolSize();
		else if(type == 1) return ((ThreadPoolExecutor)execSearch).getPoolSize();
		else return -1;
	}

	public SearchResult lookupContent(long ID, int cluster){
		return memman.searchContent(ID, cluster);
	}

	// Get an information of host.
	private void getInformation(){
		for(int i = 0; i < firstHost.length; i++){
			ArrayType infos = communicator.getInfo(firstHost[i], firstPorts[i]);
			if(infos == null) continue;
			settings.setNodeInfo(infos);
		}
	}

	// Add a node.
	public void addNode(Node newNode){
		int suspendIndex = settings.getNodeIndex(newNode);
		if(suspendIndex == -1){
			// add to chain
			settings.addToNodeList(newNode);
			memman.addCluster(settings.getNodeIndex(settings.myNode()), settings.getNodeAmount());
			refreshHashList();
		}else{
			Node nodeInList = settings.getNode(suspendIndex);
			resume(nodeInList);
		}
	}

	// Registers node.
	private int registerNode(){
		for(int i = 0; i < firstHost.length; i++){
			// Order to get node list
			try{
				for(Node eachNode: communicator.getNodeList(firstHost[i], firstPorts[i]))
					settings.addToNodeList(eachNode);
			}catch(Exception e){
				LogParser.warning("Node might be not found, retry in next node.");
				continue;
			}
			// Send own node information use with node list
			ArrayList<Node> notConnected = new ArrayList<Node>();
			boolean result;
			for(Node eachNode: settings.nodeList()){
				if(eachNode == settings.myNode()) continue;
				result = communicator.register(eachNode, settings.myNode());
				if(!result) notConnected.add(eachNode);
			}
			// Remove from list that cannot connect
			for(Node deleted: notConnected)
				settings.removeNode(deleted);

			// synchronize timings with other nodes
			for(Node eachNode: settings.nodeList()){
				if(eachNode == settings.myNode()) continue;
				communicator.synchronizeClock(eachNode);
			}
			// add myself
			refreshHashList();
			break;
		}

		return settings.getNodeIndex(settings.myNode());
	}

	public void copyItems(int clusterID, Node fromNode){
		communicator.getDatas(clusterID, fromNode, settings.myNode(), this);
	}

	// hashList needs to synchronize
	private void refreshHashList(){
		if(settings.isFullCopy()){
			for(int i = 0; i < settings.getNodeAmount(); i++){
				Node currentNode = settings.getNode(i);
				settings.addToHashList(currentNode.getAttributeHash(0), currentNode);
			}
			return;
		}

		// Get whole hash table
		for(int i = 0; i < settings.getNodeAmount(); i++){
			Node currentNode = settings.getNode(i);
			for(Long eachHash: currentNode)
				settings.addToHashList(eachHash, currentNode);
		}

		Entry<Long, Node> currentEntry = settings.firstHashEntry();
		Entry<Long, Node> nextEntry = settings.nextHashEntry(currentEntry.getKey());
		// Delete the same node in a row
		for(; nextEntry != null;){
			// Every entry can compare in pointers
			if(currentEntry.getValue().getAddr().equals(nextEntry.getValue().getAddr()))
				settings.removeHashList(currentEntry.getKey());
			// FIXME: High cost!
			currentEntry = settings.nextHashEntry(currentEntry.getKey());
			nextEntry = settings.nextHashEntry(nextEntry.getKey());
		}
		if(settings.hashListSize() != 1
			&& settings.firstHashEntry().getValue() == settings.lastHashEntry().getValue())
			settings.removeHashList(settings.lastHashEntry().getKey());
	}

	// It works for deletion.
	@Deprecated
	public void deleteReplication(String IPaddr, int port){
		Node deletedNode = Node.createTemp(IPaddr, port);
		// Node deletedInList = settings.getSameNode(deletedNode);
		// int deletedIndex = settings.getNodeIndex(deletedInList);

		// delete from chain
		settings.removeNode(deletedNode);
		refreshHashList();
	}

	public void showNodes(){
		int count = 0;
		System.out.printf("Total node: %d, Issued hash per node: %d%n", settings.getNodeAmount(),
			settings.virtualNodes());
		for(Node eachNode: settings.nodeList()){
			System.out.printf("%3d: Address-%s:%d client-%s", (++count), eachNode.getAddr(),
				eachNode.getPort(), (eachNode.client() != null? "connect   ": "disconnect"));
			System.out.printf(" 0x%016x", eachNode.getAttributeHash(0));
			if(eachNode.equals(settings.myNode())) System.out.printf(" [Running] Indexable-%s%n",
				(IndexableSwitch.isIndexable()? "Yes": "No"));
			else if(eachNode.isSuspend()) System.out.printf(" [Suspend] Left: %d sec%n", eachNode.timeLeft());
			else System.out.printf(" [Running]%n");
		}
	}

	public void showProcesses(){
		System.out.printf("Thread - Indexer/Deleter: %d, Searcher: %d%n", settings.indexThreadNum(),
			settings.searchThreadNum());
		System.out.println("Queues:");
		System.out.printf("Term:     Host %5d / Client %5d%n", TermIndexerHost.residue(),
			TermIndexerClient.residue());
		System.out.printf("Contents: Host %5d / Client %5d%n", ContentIndexerHost.residue(),
			ContentIndexerClient.residue());
		System.out
			.printf("Delete:   Host %5d / Client %5d%n", DeleterHost.residue(), DeleterClient.residue());
	}

	public SortedSet<Node> getNode(){
		return settings.nodeList();
	}

	public ContentInfo contentInfo(){
		return settings.contentInfo();
	}

	public int getCounter(){
		return indexCounter;
	}

	public int getSearchcounter(){
		return searchCounter;
	}

	public int getTerms(){
		return memman.getTerms();
	}

	public int getSkipPointerLen(){
		return memman.getSkipPointerLen(0);
	}

	public void showBackupList(){
		memman.showBackupList();
	}

	public void getMemoryList(){
		memman.getMemoryList();
	}

	public void optimizeMemory(){
		memman.optimizeMemory();
	}

	public void takeSnapshot(){
		memman.takeSnapshot();
	}

	public int getClusterBlockAmount(int clusterID){
		return memman.getClusterBlockAmount(clusterID);
	}

	// make backup file with specified entry ID
	public void backup(){
		for(Node nodes: settings.nodeList())
			if(!nodes.equals(settings.myNode())) communicator.backupLatest(nodes);
		memman.backupLatest();
		// Clear client behavior log
		try{
			TermIndexerClient.clearLog();
			ContentIndexerClient.clearLog();
		}catch(IOException e){
			System.err.println("Logger is broken.");
		}
	}

	public void revert(long entry){
		if(entry == -1L){
			for(Node nodes: settings.nodeList())
				if(!nodes.equals(settings.myNode())) communicator.revertLatest(nodes);
			memman.revertLatest();
		}else memman.revertByEntry(entry);
		// Revert behavior logs from file
		try{
			TermIndexerClient.revert();
			ContentIndexerClient.revert();
		}catch(FileNotFoundException e){
			System.err.println("Log file did not found. Data might be lost.");
		}catch(IOException e){
			System.err.println("Log file is broken. Data might be lost.");
		}
	}

	// Synchronize timings to emit beacon and backup
	public void synchronizeClock(){
		LogParser.fine("Synchronize");
		beaconThread.interrupt();
		memman.synchronizeClock();
	}

	// Suspend specified node
	public void suspend(String IPaddr, int port, long suspendTime){
		Node tempNode = Node.createTemp(IPaddr, port);
		Node nodeFromList = settings.getSameNode(tempNode);
		nodeFromList.suspend(suspendTime);
	}

	private void issueSuspendInfo(){
		File infoFile = new File(settings.directory() + ".suspendInfo");
		if(infoFile.exists()) infoFile.delete();
		try{
			infoFile.createNewFile();
		}catch(IOException e){
			System.err.println("Suspend file not issued due to interval error.");
			LogParser.fine("Error: While creating file \".suspendInfo\", IOException");
		}

		FileOutputStream stream = null;
		try{
			stream = new FileOutputStream(infoFile);
			stream.write(ByteConverter.longToByte(System.currentTimeMillis(), 8));
			stream.write(ByteConverter.longToByte(20 * 60, 8));
			stream.write(ByteConverter.longToByte(memman.latestBackupIndex(), 8));
		}catch(FileNotFoundException e){}catch(IOException e){}finally{
			if(stream != null) try{
				stream.close();
			}catch(IOException e){}
		}
	}

	// Output backup index
	private long getSuspendInfo(){
		File infoFile = new File(".suspendInfo");
		if(!infoFile.exists()) return -1;
		byte[] readbyte = new byte[8];
		long suspendTime, suspendLength;
		long ID = -1;
		FileInputStream stream = null;
		try{
			stream = new FileInputStream(infoFile);
			stream.read(readbyte);
			suspendTime = ByteConverter.byteToLong(readbyte);
			stream.read(readbyte);
			suspendLength = ByteConverter.byteToLong(readbyte);
			if(suspendTime + (suspendLength * 1000) >= System.currentTimeMillis()){
				stream.read(readbyte);
				ID = ByteConverter.byteToLong(readbyte);
			}
		}catch(FileNotFoundException e){}catch(IOException e){}finally{
			if(stream != null) try{
				stream.close();
			}catch(IOException e){}
		}
		return ID;
	}

	// Suspend my node
	public void suspendMyNode(long suspendTime){
		// Backup my datas
		// memman.backupLatest();
		IndexableSwitch.lock();
		issueSuspendInfo();
		for(Node eachNode: settings.nodeList()){
			if(eachNode != settings.myNode())
				communicator.suspend(eachNode, settings.myNode(), suspendTime, "Suspend");
		}
	}

	public void resumeMyNode(){
		getSuspendInfo();
		for(Node eachNode: settings.nodeList()){
			if(eachNode != settings.myNode()) communicator.resume(eachNode, settings.myNode());
		}
		try{
			IndexableSwitch.unlock();
		}catch(InterruptedException e){}
	}

	public void resume(String IPaddr, int port){
		Node tempNode = Node.createTemp(IPaddr, port);
		resume(tempNode);
	}

	public void resume(Node node){
		Node nodeFromList = settings.getSameNode(node);
		if(nodeFromList == null) return;
		nodeFromList.resume();
		TermIndexerHost.resumeQueue(nodeFromList);
		ContentIndexerHost.resumeQueue(nodeFromList);
	}

	public int getMyNodeIndex(){
		return settings.getNodeIndex(settings.myNode());
	}

	public void setClusterPointInfo(ArrayType info){
		memman.setClusterPointInfo(info);
	}

	public ArrayType getClusterPointInfo(int clusterID){
		return memman.getClusterPointInfo(clusterID);
	}

	public ArrayType toArrayType(int cluster, int block){
		return memman.toArrayType(cluster, block);
	}

	public void fromArrayType(int cluster, int block, int type, int subtype, byte[] array){
		memman.fromArrayType(cluster, block, type, subtype, array);
	}

	public int getDeleteKeyType(){
		return settings.deleteType();
	}
}
